/**
 * Esta clase define un hilo que atiende a una conexión
 * DCLI que se haya realizado a un proceso DMon.
 *
 * @author Colmenares Carlos (06-39380)
 * @author Tough Alex (07-41604)
 */
import java.net.*;
import java.io.*;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
//import java.util.Scanner;
class HiloDMon extends Thread {

    /**
     * Socket por el cual se realiza la comunicación con
     * el cliente. Si este valor es nulo, entonces la
     * comunicación se estará realizando directa con un prompt.
     */
    private Socket socketCliente;
    
    /**
     * Directorio usado como repositorio
     */
    private File repositorio;

    /**
     * Esta variable indica si el cliente es atentido
     * por un shell, de modo que hay que hacer preguntas
     * por línea de comandos
     */
    private boolean prompt;

    /**
     * Esta inastancia se usa para escribir texto a
     * la salida que espere por respuestas
     */
    private PrintStream out;
    
    /**
     * Esta instancia se usa para leer mensajes del
     * cliente
     */
    private BufferedReader in;
    //private Scanner in;

    /**
     * Esta estructura contiene todos los usuarios
     * que están conectados a algún DRep que a su
     * vez está asociado a este DMon
     */
    private Set<String> registroDeUsuarios;

    /**
     * Este atributo contiene, por cada drep, los usuarios
     * conectados a él.
     */
    private Map<String, Set<String> > registroDeDRep;

    /**
     * El primer valor contiene las operaciones
     * de UPloawd, el segundo de DOWNload y el
     */
    private int[] logDMon;

    /**
     * Este será un string que identificará a este
     * hilo y su conección con un DRep
     */
    private String url;

    /**
     * Cuando esta variable es verdadera, el hilo
     * imprime información inermedia acerca de su
     * ejecución y estado.
     */
    private boolean verboso;


    HiloDMon(Set<String> registroDeUsuarios, Map<String,Set<String> > registroDeDRep,
            int[] logDMon, boolean verboso){
        this.socketCliente = null;
        this.registroDeUsuarios = registroDeUsuarios;
        this.registroDeDRep = registroDeDRep;
        this.logDMon = logDMon;
        this.verboso = verboso;
        prompt = true;
    }

    HiloDMon(Socket socketCliente, Set<String> registroDeUsuarios,
            Map<String,Set<String> > registroDeDRep,
            int[] logDMon, boolean verboso){
        this.socketCliente = socketCliente;
        this.registroDeUsuarios = registroDeUsuarios;
        this.registroDeDRep = registroDeDRep;
        this.logDMon = logDMon;
        this.verboso = verboso;
        prompt = false;

    }

    public void run() {

        if(verboso){
            System.out.println("Se ha creado un nuevo hilo!");
            System.out.flush();
        }
   
        try{
        
            establecerES();
        
            //Esperar por comandos
            while(true){
                
                String comando = leerComando();

                //Parsear el comando. En caso de que sea un comando
                //válido, se responderá con un string aprobatorio
                //primero

                //UP
                if( comando.equals(ProtocoloDMon.UP) && !prompt ){

                    synchronized(logDMon){
                        logDMon[0]++;
                    }
                    out.println(ProtocoloDMon.OK);
                }
                //DOWN
                else if( comando.equals(ProtocoloDMon.DOWN) && !prompt ){

                    synchronized(logDMon){
                        logDMon[1]++;
                    }
                    out.println(ProtocoloDMon.OK);
                }
                //TUSER
                else if( comando.equals(ProtocoloDMon.TUSER) ){

                    Object[] su = registroDeUsuarios.toArray();

                    if(!prompt){
                        out.println(ProtocoloDMon.OK);
                        out.println(su.length);
                    }
                    for(int i=0;i<su.length;i++){
                        out.println((String)su[i]);
                    }
                }
                //NOPS
                else if( comando.equals(ProtocoloDMon.NOPS) && prompt ){

                    out.println("Total Uploads: "+logDMon[0]);
                    out.println("Total Downloads: "+logDMon[1]);
                }
                //NREP
                else if( comando.equals(ProtocoloDMon.NREP) && !prompt){

                    Set<String> dreps = registroDeDRep.keySet();

                    Object[] su = dreps.toArray();

                    out.println(ProtocoloDMon.OK);
                    out.println(su.length);
                    for(int i=0;i<su.length;i++){
                        out.println((String)su[i]);
                    }
                }
                //NEWUSER
                else if( comando.equals(ProtocoloDMon.NEWUSER) && !prompt){
                    
                    String uname = in.readLine();
                    registroDeUsuarios.add(uname);
                    registroDeDRep.get(url).add(uname);
                    out.println(ProtocoloDMon.OK);
                    out.flush();

                }


                //salir
                else if( comando.equals(ProtocoloDMon.SALIR) ){
                    break;
                }

                //error de protocolo
                else {
                    if(prompt){
                        out.println("Comando no permitido, ejecute el "+
                                "comando 'ayuda' para una lista "+
                                "detallada de comandos permitidos");
                        out.flush();
                    }
                    else {
                        out.println(Protocol.ERROR_DE_SINTAXIS);
                        out.flush();
                        throw new IOException("Error de protocolo, "+
                                "comando no reconocido"); 
                    }
                }
            }
            in.close();

        } catch (NumberFormatException nfe) {
            
            out.println(ProtocoloDMon.ERROR_DE_SINTAXIS);

        } catch (IOException e) {
        
            out.println(ProtocoloDMon.ERROR_INESPERADO);
            System.err.println("Error leyendo/escribiendo");
            System.err.println(e);
        
        } finally {

            Set<String> users = registroDeDRep.get(url);

            Object[] us = users.toArray();
            for(int i=0;i<us.length;i++){
                registroDeUsuarios.remove((String)us[i]);
            }
            registroDeDRep.remove(url);
            out.close();
        
        }

    }

    /**
     * Este procedimiento establece el valor de las variables
     * de entrada y salida dependiento si la E/S se realizará
     * por cónsola o por un socket
     */
    private void establecerES() throws IOException {
        if(!prompt){
            out = new PrintStream(socketCliente.getOutputStream(), true);
            in = new BufferedReader(
                    new InputStreamReader(
                        socketCliente.getInputStream()));//,"US-ASCII"));
            //in = new Scanner(socketCliente.getInputStream());

            //Ahora esperamos por el comando de iniciar protocolo
            //para validar la conexión
            String inicio = in.readLine();

            if(!inicio.equals(ProtocoloDMon.CONNECT) ){
                throw new IOException("Error de protocolo");
            }
            else {
                out.println(ProtocoloDMon.OK);
                out.flush();

                //String host = socketCliente.getInetAddress().getHostName();
                //String url = host+":"+socketCliente.getPort();
                url = socketCliente.toString();
                registroDeDRep.put( url, new HashSet<String>() );

            }

            if(verboso){
                System.out.println("Se conectó el usuario: "+url);
                System.out.flush();
            }

        } else {

            out = System.out;
            //in = new Scanner(System.in);
            in = new BufferedReader(new InputStreamReader(System.in));//,"US-ASCII"));

            out.println( "Ingrese el comando 'ayuda' (sin comillas) "+
                    "para información detallada");
            out.flush();
        }
    }

    /**
     * Esta función lee un comando, bien sea del socket o
     * o de la línea de comandos. En caso de haber un error
     * de protocolo, se reporta el error.
     *
     * @return  Si el comando fue leído exitosamente, se retorna
     *          dicho comando. En caso contrario, null es retornado.
     */
    private String leerComando() throws IOException {

        if(prompt){
            out.print("dmon> ");
            out.flush();
        }

        String comando = in.readLine();

        if(verboso){
            System.out.println("Recibí el comando: "+comando);
            System.out.flush();
        }

        if(comando==null)
            throw new IOException(
                    "La conexión se ha cerrado "+
                    "inesperadamente");

        if(!prompt){
            return comando;
        }

        comando = comando.trim();

        if(comando.length()==0)
            return leerComando();
        else if(comando.equals("ayuda")){
            out.println(
                "Ingrese el comando que desee que sea ejecutado, los "+
                "comandos reconocidos son los siguientes:"
            );
            out.println();
            out.println(
                "\tayuda:    Imprime este menú"        
            );
            out.println();
            out.println(
                "\ttuser:    Muestra una lista de todos los usuarios "+
                "conectados al sistema"
            );
            out.println();
            out.println(
                "\tnops:     Muestra el número de Down/Uploads que se "+
                "han realizado desde todos los DReps conectados a este DMon"
            );
            out.println();
            out.println(
                "\tsalir:    Terminar el programa y todos sus hilos"
            );

            out.flush();
            return leerComando();
        }
        return "$"+comando;
    }


    /**
     * Esta función abre y lee un archivo y sus contenidos los
     * escribe por el PrintWriter almacenado en el atributo de la
     * clase 'out'
     * @param nombreDeArchivo El nombre del archivo a abrir
     */
    void leerYEscribirArchivo(String nombreDeArchivo) throws IOException {
        File archivo = new File(repositorio.getName()+"/"
                +nombreDeArchivo);

        if(!archivo.exists() || !archivo.isFile()){
            out.println(Protocol.ARCHIVO_NO_ENCONTRADO);
            throw new IOException("Error de protocolo"); //TODO
        }

        //Si el archivo es muy grande, abortar
        if( archivo.length() > Integer.MAX_VALUE/2 ){
            out.println(Protocol.ERROR_INESPERADO);
            throw new IOException("Archivo muy grande! "
                    +nombreDeArchivo);
        }

        char[] streamDeChars = new char[(int)archivo.length()];

        //FileInputStream fis = new FileInputStream(archivo);
        //Ffis.read(streamDeChars, 0, (int)archivo.length());

        InputStreamReader isr = 
            new InputStreamReader(new FileInputStream(archivo));//,"US-ASCII");
        isr.read(streamDeChars, 0, (int)archivo.length());

        if(!prompt){
            out.println(Protocol.OK);
            out.println(archivo.length());
        }

        //out.write(streamDeChars,0,(int)archivo.length());
        out.print(streamDeChars);

        out.flush();
        isr.close();
    }
}


